/**
 * Copyright 2010 Tristan Slominski
 *
 * Licensed under the MIT License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.opensource.org/licenses/mit-license.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mindash.handlebars.client.presenters.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.mindash.handlebars.client.constants.HandlebarsConstants;
import com.mindash.handlebars.client.events.RefreshViewEvent;
import com.mindash.handlebars.client.events.ReloadEvent;
import com.mindash.handlebars.client.places.AtPlace;
import com.mindash.handlebars.client.places.GoToPlace;
import com.mindash.handlebars.client.places.handlers.AtPlaceGenericHandler;
import com.mindash.handlebars.client.presenters.Presenter;
import com.mindash.handlebars.client.services.EventBus;
import com.mindash.handlebars.client.services.RpcService;
import com.mindash.handlebars.client.views.View;

/**
 * @author Tristan Slominski
 * 
 */
public abstract class DefaultPresenterImpl<V extends View> implements Presenter {

  /* injected dependencies */
  @Inject
  protected RpcService rpcService;

  /* constructed dependencies */
  protected EventBus eventBus;
  protected Provider<V> viewProvider;

  /* variables */
  private int adapter;
  private AtPlace<?> currentPlace;
  private boolean loading = false;
  private Map<String, String> params = new HashMap<String, String>();
  private List<String> placeChangeParamNames = new ArrayList<String>();
  private V view;

  @Inject
  public DefaultPresenterImpl(EventBus eventBus, Provider<V> viewProvider) {
    this.eventBus = eventBus;
    this.viewProvider = viewProvider;
  }

  /**
   * Fires a refresh event. This is usually done after one of the presenters
   * updates their view and needs to tell the page presenter to refresh. This
   * event does not cause {@code onAtPlace()} method to be executed on any
   * presenter.
   */
  public void fireRefreshEvent() {
    eventBus.fireEvent(new RefreshViewEvent(getCurrentPlace()));
  }

  /**
   * Fires a reload event. This event causes an entire page refresh where all
   * presenters will execute their {@code onAtPlace()} methods.
   */
  public void fireReloadEvent() {
    eventBus.fireEvent(new ReloadEvent());
  }

  /**
   * Ensures that this presenter will execute {@code onAtPlace()} method even if
   * place parameters did not change.
   */
  public void forceOnAtPlaceExecution() {
    for (String param : getPlaceChangeParamNames()) {
      getParams().put(param, HandlebarsConstants.onAtPlaceExecutionGuarantee);
    }
  }

  /**
   * Convenience method that assembles the {@code GoToPlace} event before
   * putting it on the {@code EventBus}.
   * 
   * @param goToPlace the place to go to
   * @param params the place parameters
   */
  public void goToPlaceWithParams(GoToPlace<?> goToPlace,
      Map<String, String> params) {
    goToPlace.setParams(params);
    eventBus.fireEvent(goToPlace);
  }

  @Override
  public View makeView() {
    setView(viewProvider.get());
    getView().setAdapter(getAdapter());
    getView().setPresenter(this);
    return getView();
  }

  /**
   * Sets the current place variable for the presenter and creates a new view
   * instance. Override this method but be sure to include a call to {@code
   * super.onAtPlace(place)} to make sure the presenter correctly sets its
   * current place.
   * 
   * @param place the place the presenter is at
   */
  @Override
  public <T extends AtPlaceGenericHandler> void onAtPlace(AtPlace<T> place) {
    setCurrentPlace(place);
    makeView();
  }

  /**
   * <p>
   * Checks if the current place changed in respect to the parameters that this
   * presenter cares about. Use {@link #getPlaceChangeParamNames()} to set
   * parameters to check.</p>
   * <p>
   * This check is a one-shot-per-place check. This is because after this method
   * completes, the presenter now stored the current place values, so when
   * called again on the same place, the method will return {@code false}</p>
   * 
   * @return {@code true} if at least one parameter is different, {@code false}
   * otherwise
   */
  public boolean placeChanged() {
    boolean result = false;
    for (String param : getPlaceChangeParamNames()) {
      String value = getParams().get(param);
      if (value == null) {
        if (getCurrentPlace().getParams().get(param) != null) {
          result = true;
          storeParams();
          break;
        }
      } else {
        if (!value.equals(getCurrentPlace().getParams().get(param))) {
          result = true;
          storeParams();
          break;
        }
      }
    }
    return result;
  }
  
  @Override
  public void postProcessView() {
    // nothing for default abstract
  }

  /**
   * Stores the specified params for future place change verification.
   */
  public void storeParams() {
    for (String param : getPlaceChangeParamNames()) {
      getParams().put(param, getCurrentPlace().getParams().get(param));
    }
  }

  /* getters and setters */

  @Override
  public int getAdapter() {
    return adapter;
  }

  public AtPlace<?> getCurrentPlace() {
    return currentPlace;
  }

  public Map<String, String> getParams() {
    return params;
  }

  public List<String> getPlaceChangeParamNames() {
    return placeChangeParamNames;
  }

  public V getView() {
    return view;
  }

  @Override
  public boolean isLoading() {
    return loading;
  }

  @Override
  public void setAdapter(int adapter) {
    this.adapter = adapter;
  }

  public void setCurrentPlace(AtPlace<?> currentPlace) {
    this.currentPlace = currentPlace;
  }

  @Override
  public void setLoading(boolean loading) {
    this.loading = loading;
  }

  public void setParams(Map<String, String> params) {
    this.params = params;
  }

  public void setPlaceChangeParamNames(List<String> placeChangeParamNames) {
    this.placeChangeParamNames = placeChangeParamNames;
  }

  public void setView(V view) {
    this.view = view;
  }

}
